package com.nulldev.util.scripting.cup.vee.runner;

import static com.nulldev.util.scripting.cup.vee.utils.VEEConstants.USE_SIL_REV_1;
import static com.nulldev.util.scripting.cup.vee.utils.VEEConstants.isWithinBounds;
import static com.nulldev.util.scripting.cup.vee.utils.VEEOperationManager.checkIfCompatTypes;
import static com.nulldev.util.scripting.cup.vee.utils.VEEOperationManager.doOperation;
import static com.nulldev.util.scripting.cup.vee.utils.VEEOperationManager.VEEOperationType.ADD0;
import static com.nulldev.util.scripting.cup.vee.utils.VEEOperationManager.VEEOperationType.AND1;
import static com.nulldev.util.scripting.cup.vee.utils.VEEOperationManager.VEEOperationType.DIV0;
import static com.nulldev.util.scripting.cup.vee.utils.VEEOperationManager.VEEOperationType.MOD0;
import static com.nulldev.util.scripting.cup.vee.utils.VEEOperationManager.VEEOperationType.MUL0;
import static com.nulldev.util.scripting.cup.vee.utils.VEEOperationManager.VEEOperationType.NAND1;
import static com.nulldev.util.scripting.cup.vee.utils.VEEOperationManager.VEEOperationType.NOT1;
import static com.nulldev.util.scripting.cup.vee.utils.VEEOperationManager.VEEOperationType.OR1;
import static com.nulldev.util.scripting.cup.vee.utils.VEEOperationManager.VEEOperationType.SUB0;
import static com.nulldev.util.scripting.cup.vee.utils.VEEOperationManager.VEEOperationType.XOR1;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.internal.backport.concurrency9.Lists;
import com.nulldev.util.scripting.cup.instruction_sets.sil.insts.AddOperation;
import com.nulldev.util.scripting.cup.instruction_sets.sil.insts.CUPIDQuery;
import com.nulldev.util.scripting.cup.instruction_sets.sil.insts.DivideOperation;
import com.nulldev.util.scripting.cup.instruction_sets.sil.insts.LoadNearestRegister;
import com.nulldev.util.scripting.cup.instruction_sets.sil.insts.LoadRegister;
import com.nulldev.util.scripting.cup.instruction_sets.sil.insts.ModulusOperation;
import com.nulldev.util.scripting.cup.instruction_sets.sil.insts.MoveRegister;
import com.nulldev.util.scripting.cup.instruction_sets.sil.insts.MultiplyOperation;
import com.nulldev.util.scripting.cup.instruction_sets.sil.insts.NukeRegister;
import com.nulldev.util.scripting.cup.instruction_sets.sil.insts.SubtractOperation;
import com.nulldev.util.scripting.cup.instruction_sets.sil.insts.WriteToRegister;
import com.nulldev.util.scripting.cup.instruction_sets.smi.insts.LoadStringRegister;
import com.nulldev.util.scripting.cup.instruction_sets.sti.insts.ConsoleOut;
import com.nulldev.util.scripting.cup.instructions.CupInstruction;
import com.nulldev.util.scripting.cup.vee.CUPRegister;
import com.nulldev.util.scripting.cup.vee.CUPRegister.CUPRegisterType;
import com.nulldev.util.scripting.cup.vee.VEE;
import com.nulldev.util.scripting.cup.vee.VEERegisterManager;
import com.nulldev.util.scripting.cup.vee.registers.StringCUPRegister;
import com.nulldev.util.scripting.cup.vee.utils.VEEOperationManager.VEEOperationType;

public class CUPRunner implements Runnable {

	private final List<CupInstruction> inst;
	private final VEE vee;
	private final VEERegisterManager regs;

	public CUPRunner(final VEE vee, final List<CupInstruction> insts) {
		this.vee = vee;
		this.inst = insts;
		this.regs = vee.getRegisterManager();
	}

	private void checkIfArgsNotBlank(final Object[] args) {
		if (args == null || args.length == 0)
			throw new IllegalArgumentException("checkIfArgsNotBlank: ARGS IS EITHER NULL OR BLANK");
	}

	private void checkIfNotNull(final Object obj) {
		if (obj == null)
			throw new NullPointerException("checkIfNotNull: OBJECT IS NULL!");
	}

	private void checkIfNumericalRegister(final CUPRegister<?> reg) {
		switch (reg.getRegisterType()) {
			case SHORT:
			case NORMAL:
			case WIDE:
				return;
			default:
				throw new IllegalArgumentException("Register is not a numerical type!");
		}
	}

	private void checkIfRegisterType(final CUPRegister<?> reg, final CUPRegisterType type) {
		if (reg == null || type == null)
			throw new NullPointerException("reg OR type is null!");
		if (reg.getRegisterType().getType() != type.getType())
			throw new IllegalArgumentException("Register is not of expected type " + type + "!");
	}

	private void checkIfSameTypes(final CUPRegister<?> regA, final CUPRegister<?> regB) {
		if (regA == null)
			throw new NullPointerException("reg == null!");
		if (regB == null)
			throw new NullPointerException("regB == null!");
		if (!regA.isSameAs(regB))
			throw new IllegalArgumentException("regB is not the same type as regA.");
	}

	private CUPRegister<?> getRegister(final Object obj) {
		if (obj == null)
			throw new NullPointerException("getRegister: OBJECT IS NULL!");
		return this.vee.getRegisterManager().getRegister(obj);
	}

	@SuppressWarnings(
		{ "rawtypes", "unchecked" })
	@Override
	public void run() {
		try {
			if (this.inst == null) {
				this.exception(-1);
				return;
			}
			final short defaultRegTypeS = this.vee.getRegisterManager().getDefaultType().getType();
			loop: {
				for (int i = 0; i < this.inst.size(); i++) {
					final CupInstruction inst = this.inst.get(i);
					switch (inst.getInstructionID()) {
						case 0:
							/* NULL or NOP */ break;
						/* SIL: Simple Instruction Language */
						case 1: /* LNR */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object item = args[0];
							final Object type = args.length == 2 ? args[1] : null;
							if (type != null) {
								if (!(type instanceof Short || type instanceof Integer)) {
									this.exception("Cannot use '" + type + "' as type.", i);
									break loop;
								}
								regs.makeOT(item, MathUtil.autoConvertToShort(type));
								break;
							} else {
								regs.make(item);
								break;
							}
						}
						case 2: /* LRG */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object item = args[0];
							checkIfNotNull(item);
							if (!(item instanceof Integer || item instanceof Short)) {
								this.exception("Cannot use '" + item + "' as number.", i);
								break loop;
							}
							final Object type = args.length == 2 ? args[1] : null;
							if (type != null && !(type instanceof Integer || type instanceof Short)) {
								this.exception("Cannot use '" + type + "' as number.", i);
								break loop;
							}
							final short ttype = type != null ? (short) type : defaultRegTypeS;
							regs.makeIT((int) item, ttype);
							break;
						}
						case 3: /* MOV */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object regA = getRegister(args[0]);
							checkIfNotNull(regA);
							if (!(regA instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + regA + "' as register A.", i);
								break loop;
							}
							final Object regB = getRegister(args[1]);
							checkIfNotNull(regB);
							if (!(regB instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + regB + "' as register B.", i);
								break loop;
							}
							setAndConvertIfRequired(((CUPRegister) regB), ((CUPRegister) regA).get());
							((CUPRegister) regA).reset();
							break;
						}
						case 4: /* CPY */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object regA = getRegister(args[0]);
							checkIfNotNull(regA);
							if (!(regA instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + regA + "' as register A.", i);
								break loop;
							}
							final Object regB = getRegister(args[1]);
							checkIfNotNull(regB);
							if (!(regB instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + regB + "' as register B.", i);
								break loop;
							}
							((CUPRegister) regB).set(((CUPRegister) regA).get());
							break;
						}
						case 5: /* WTR */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object _regA = getRegister(args[0]);
							checkIfNotNull(_regA);
							if (!(_regA instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + _regA + "' as register.", i);
								break loop;
							}
							final CUPRegister regA = ((CUPRegister) _regA);
							final Object writeItem = args[1];
							checkIfNotNull(writeItem);
							if (!checkIfValidObject((CUPRegister<?>) regA, writeItem)) {
								this.exception("Cannot use '" + writeItem + "' (" + writeItem.getClass() + ") as write item.", i);
								break loop;
							}
							writeToRegister(regA, writeItem);
							break;
						}
						case 6: /* ADD */
						case 7: /* SUB */
						case 8: /* MUL */
						case 9: /* DIV */
						case 10:/* MOD */ {
							VEEOperationType opType;
							switch (inst.getInstructionID()) {
								case 6: {
									opType = ADD0;
									break;
								}
								case 7: {
									opType = SUB0;
									break;
								}
								case 8: {
									opType = MUL0;
									break;
								}
								case 9: {
									opType = DIV0;
									break;
								}
								case 10: {
									opType = MOD0;
									break;
								}
								default: {
									this.exception("Invalid instruction for opType: " + inst.getInstructionID(), i);
									break loop;
								}
							}
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object _regA = getRegister(args[0]);
							checkIfNotNull(_regA);
							if (!(_regA instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + _regA + "' as register.", i);
								break loop;
							}
							final CUPRegister regA = ((CUPRegister) _regA);
							Object objB = args[1];
							checkIfNotNull(objB);
							if (!(objB instanceof CUPRegister<?>) && !(objB instanceof Number) && this.vee.getRegisterManager().isRegister(objB)) {
								objB = getRegister(objB);
							}
							if (objB instanceof CUPRegister<?>) {
								checkIfSameTypes(regA, (CUPRegister<?>) objB);
								doOperation(opType, regA, objB);
							} else if (objB instanceof Number) {
								checkIfCompatTypes(regA, objB);
								doOperation(opType, regA, objB);
							} else if (objB instanceof String && StringsUtil.isNumber((String) objB)) {
								objB = StringsUtil.asNumber((String) objB);
								checkIfCompatTypes(regA, objB);
								doOperation(opType, regA, objB);
							} else {
								this.exception("Cannot use '" + objB + "' as " + opType + " value.", i);
								break loop;
							}
							break;
						}
						case 11: /* NAND */
						case 12: /* AND */
						case 13: /* OR */
						case 14: /* XOR */
						case 15: /* NOT */ {
							VEEOperationType opType;
							switch (inst.getInstructionID()) {
								case 11: {
									opType = NAND1;
									break;
								}
								case 12: {
									opType = AND1;
									break;
								}
								case 13: {
									opType = OR1;
									break;
								}
								case 14: {
									opType = XOR1;
									break;
								}
								case 15: {
									opType = NOT1;
									break;
								}
								default: {
									this.exception("Invalid instruction for opType: " + inst.getInstructionID(), i);
									break loop;
								}
							}
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object _regA = getRegister(args[0]);
							checkIfNotNull(_regA);
							if (!(_regA instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + _regA + "' as register.", i);
								break loop;
							}
							final CUPRegister regA = ((CUPRegister) _regA);
							final Object objB = args[1];
							checkIfNotNull(objB);
							final Object objC = args.length == 3 ? args[2] : null;
							if (objC != null && !(objC instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + objC + "' as register.", i);
								break loop;
							}
							if (objB instanceof CUPRegister<?>) {
								checkIfSameTypes(regA, (CUPRegister<?>) objB);
								if (objC != null) {
									checkIfSameTypes(regA, (CUPRegister<?>) objC);
									doOperation(opType, regA, (CUPRegister<?>) objC, objB);
								} else
									doOperation(opType, regA, objB);
							} else {
								checkIfCompatTypes(regA, objB);
								if (objC != null) {
									checkIfSameTypes(regA, (CUPRegister<?>) objC);
									doOperation(opType, regA, (CUPRegister<?>) objC, objB);
								} else
									doOperation(opType, regA, objB);
							}
							break;
						}
						case 16: /* EINT */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object _regA = getRegister(args[0]);
							checkIfNotNull(_regA);
							if (!(_regA instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + _regA + "' as register.", i);
								break loop;
							}
							final CUPRegister regA = ((CUPRegister) _regA);
							checkIfNumericalRegister(regA);
							final Number n = (Number) regA.get();
							if (n.intValue() != 1) {
								this.exception("Invoked via EINT", i);
								break loop;
							}
							break;
						}
						case 17: /* HINT */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object _regA = getRegister(args[0]);
							checkIfNotNull(_regA);
							if (!(_regA instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + _regA + "' as register.", i);
								break loop;
							}
							final CUPRegister regA = ((CUPRegister) _regA);
							checkIfNumericalRegister(regA);
							final Number n = (Number) regA.get();
							if (n.intValue() != 1)
								break loop;
							break;
						}
						// TODO: Add instructions 18 to 20.
						case 21: /* CUPID */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object _queryItem = args[0];
							checkIfNotNull(_queryItem);
							if (!(_queryItem instanceof Short) && !(_queryItem instanceof Integer)) {
								this.exception("Cannot use '" + _queryItem + "' as short. '" + _queryItem + "' is type '" + _queryItem.getClass() + "'.", i);
								break loop;
							}
							final short queryItem = MathUtil.autoConvertToShort(_queryItem);
							final Object _register = getRegister(args[1]);
							checkIfNotNull(_register);
							if (!(_register instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + _register + "' as register.", i);
								break loop;
							}
							final CUPRegister register = (CUPRegister<?>) _register;
							final short queryFor = queryItem;
							switch (queryFor) {
								case 1: /* VEE_VENDOR */ {
									writeToRegister(register, "nullUtil");
									break;
								}
								case 2: /* VEE_NAME */ {
									writeToRegister(register, "nullUtil's Virtual Execution Environment (VEE)");
									break;
								}
								case 3: /* VEE_MODEL */ {
									writeToRegister(register, "Generic Implementation for CUP");
									break;
								}
								case 4: /* VEE_VERSION */ {
									writeToRegister(register, 0x1 /* 1.0 */);
									break;
								}
								case 5: /* NSL_SUPPORTED_FLAG */ {
									writeToRegister(register, 0);
									break;
								}
								case 6: /* STI_SUPPORTED_FLAG */ {
									writeToRegister(register, 1);
									break;
								}
								case 7: /* NMI_SUPPORTED_FLAG */
								case 8: /* ESI_SUPPORTED_FLAG */
								case 9: /* SII_SUPPORTED_FLAG */
								case 10: /* SIE_SUPPORTED_FLAG */
								case 11: /* FVO_SUPPORTED_FLAG */ {
									writeToRegister(register, 0);
									break;
								}
								case 12: /* SMI_SUPPORTED_FLAG */ {
									writeToRegister(register, 1);
									break;
								}
								case 97: /* IS_NATIVE_FLAG */ {
									writeToRegister(register, 0);
									break;
								}
								case 98: /* GET_NATIVE_ARCH */ {
									writeToRegister(register, 100); /* UNKNOWN */
									break;
								}
								case 99: /* GET_CORE_COUNT */ {
									writeToRegister(register, Runtime.getRuntime().availableProcessors());
									break;
								}
								case 100: /* GET_MEMORY_AMOUNT_KB */ {
									writeToRegister(register, Runtime.getRuntime().maxMemory() / 1024);
									break;
								}
								case 101: /* GET_MEMORY_USABLE_AMOUNT_KB */ {
									writeToRegister(register, Runtime.getRuntime().freeMemory() / 1024);
									break;
								}
								case 102: /* GET_MEMORY_AMOUNT_BYTES */ {
									writeToRegister(register, Runtime.getRuntime().maxMemory());
									break;
								}
								case 103: /* GET_MEMORY_USABLE_AMOUNT_BYTES */ {
									writeToRegister(register, Runtime.getRuntime().freeMemory());
									break;
								}
								case 0: /* NULL */
								default:
									break;
							}
							break;
						}
						case 22: /* REGTF */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object _regA = getRegister(args[0]);
							checkIfNotNull(_regA);
							if (!(_regA instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + _regA + "' as register.", i);
								break loop;
							}
							final Object _register = getRegister(args[1]);
							checkIfNotNull(_register);
							if (!(_register instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + _register + "' as register.", i);
								break loop;
							}
							final CUPRegister register = (CUPRegister<?>) _register;
							checkIfRegisterType(register, CUPRegisterType.SHORT);
							register.set(((CUPRegister) _regA).getRegisterType().getType());
							break;
						}
						case 23: /* HLT */ {
							break loop;
						}
						case 24: /* NUKE */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object _regA = getRegister(args[0]);
							checkIfNotNull(_regA);
							if (!(_regA instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + _regA + "' as register.", i);
								break loop;
							}
							if (this.regs.isProtected(args[0])) {
								this.exception("Register '" + _regA + "' is protected!", i);
								break loop;
							}
							this.regs.removeRegister(args[0]);
							break;
						}
						case 25: /* INC */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object _regA = getRegister(args[0]);
							checkIfNotNull(_regA);
							if (!(_regA instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + _regA + "' as register.", i);
								break loop;
							}
							final Object incAm = args.length == 2 ? args[1] : null;
							if (incAm == null) {
								checkIfNumericalRegister((CUPRegister<?>) _regA);
								final CUPRegister<?> x = ((CUPRegister<?>) _regA);
								doOperation(ADD0, x, 1);
							} else {
								checkIfNumericalRegister((CUPRegister<?>) _regA);
								final CUPRegister<?> x = ((CUPRegister<?>) _regA);
								doOperation(ADD0, x, incAm);
							}
							break;
						}
						case 26: /* DEC */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object _regA = getRegister(args[0]);
							checkIfNotNull(_regA);
							if (!(_regA instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + _regA + "' as register.", i);
								break loop;
							}
							final Object decAm = args.length == 2 ? args[1] : null;
							if (decAm == null) {
								checkIfNumericalRegister((CUPRegister<?>) _regA);
								final CUPRegister<?> x = ((CUPRegister<?>) _regA);
								doOperation(SUB0, x, 1);
							} else {
								checkIfNumericalRegister((CUPRegister<?>) _regA);
								final CUPRegister<?> x = ((CUPRegister<?>) _regA);
								doOperation(SUB0, x, decAm);
							}
							break;
						}
						case 27: /* PFCH */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object _regA = getRegister(args[0]);
							checkIfNotNull(_regA);
							if (!(_regA instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + _regA + "' as register.", i);
								break loop;
							}
							checkIfNumericalRegister(((CUPRegister<?>) _regA));
							int nextInstruction;
							if (i >= this.inst.size()) {
								nextInstruction = 0;
							} else {
								nextInstruction = this.inst.get(i + 1).getInstructionID();
							}
							writeToRegister(((CUPRegister<?>) _regA), nextInstruction);
							break;
						}
						/* STI: Simple Terminal Interface */
						case 4096: /* COUT */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							if (args.length == 1) {
								JVM.println("=> " + args[0]);
							} else {
								JVM.println("=> " + Arrays.toString(args));
							}
							break;
						}
						/* SMI: String Manipulation Interface */
						case 5020: /* LSR */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object regAl = args[0];
							checkIfNotNull(regAl);
							final Object item = args[1];
							checkIfNotNull(item);
							if (!(item instanceof String)) {
								this.exception("Cannot use '" + item + "' as string.", i);
								break loop;
							}
							if (USE_SIL_REV_1) {
								((StringCUPRegister) regs.makeOT(regAl, CUPRegisterType.STRING.getType())).set((String) item);
							} else {
								regs.makeCustom(regAl, item);
							}
							break;
						}
						case 5021: /* LBSR */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object reg0 = args[0];
							checkIfNotNull(reg0);
							final Object length = args[1];
							checkIfNotNull(length);
							final CUPRegister<?> reg = getRegister(reg0);
							if (!(reg instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + reg + "' as register.", i);
								break loop;
							}
							final int times = MathUtil.autoConvertToInt(length);
							writeToRegister(reg, StringsUtil.repeat(" ", times));
							break;
						}
						case 5022: /* SLEN */ {
							final Object[] args = inst.getInstructionArguments();
							checkIfArgsNotBlank(args);
							final Object item = args[0];
							checkIfNotNull(item);
							final Object output = args[1];
							checkIfNotNull(output);
							if (!(output instanceof CUPRegister<?>)) {
								this.exception("Cannot use '" + output + "' as register.", i);
								break loop;
							}
							checkIfNumericalRegister(((CUPRegister<?>) output));
							if (item instanceof String) {
								final String s = (String) item;
								final CUPRegister<?> out = (CUPRegister<?>) output;
								writeToRegister(out, s.length());
								break;
							} else if (item instanceof StringCUPRegister && USE_SIL_REV_1) {
								final String s = (((StringCUPRegister) item)).get();
								final CUPRegister<?> out = (CUPRegister<?>) output;
								writeToRegister(out, s.length());
								break;
							} else {
								this.exception("Cannot use '" + item + "' as string.", i);
								break loop;
							}
						}
						default: {
							this.exception("Hit unknown instruction, " + MathUtil.toHex(inst.getInstructionID()), i);
							break loop;
						}
					}
				}
			}
			// goto loop;
			// If only Java supported goto's within code :(
		} catch (Throwable t) {
			JVM.errorln(t);
			this.exception("Hit an internal exception.", 0);
		}
	}

	@SuppressWarnings("unchecked")
	private void setAndConvertIfRequired(@SuppressWarnings("rawtypes") final CUPRegister register, final Object object) {
		switch (register.getRegisterType()) {
			case NORMAL: {
				if (object instanceof Short) {
					register.set((int) (short) object);
					break;
				}
			}
			case EXTENDED: {
				if (object instanceof Short) {
					register.set((long) (short) object);
					break;
				}
			}
			default: {
				register.set(object);
				break;
			}
		}
	}

	@SuppressWarnings("unchecked")
	private void writeToRegister(@SuppressWarnings("rawtypes") final CUPRegister reg, final Object item) {
		switch (reg.getRegisterType()) {
			case SHORT: {
				reg.set(MathUtil.autoConvertToShort(item));
				break;
			}
			case NORMAL: {
				reg.set(MathUtil.autoConvertToInt(item));
				break;
			}
			case WIDE: {
				reg.set(MathUtil.autoConvertToLong(item));
				break;
			}
			default: {
				reg.set(item);
				break;
			}
		}
	}

	private boolean checkIfValidObject(final CUPRegister<?> reg, final Object item) {
		switch (reg.getRegisterType()) {
			case WIDE:
				return item instanceof BigInteger;
			case EXTENDED:
				return item instanceof Long;
			case SHORT:
				return isWithinBounds(reg.getRegisterType(), item);
			case NORMAL:
				return item instanceof Integer;
			case DOUBLE:
				return item instanceof Double;
			case FLOAT_SFP:
			case FLOAT:
				return item instanceof Float;
			case STRING:
				return item instanceof String;
			case CUSTOM:
				return item.getClass().isInstance(reg.get());
			default:
				return false;
		}
	}

	private void exception(final int index) {
		this.exception(null, index);
	}

	private void exception(final String reason, final int index) {
		JVM.println("===============  CUP :( ===============");
		JVM.println();
		JVM.println("Your VEE (Virtual Execution Environment) has hit an exception.");
		if (reason != null && !reason.isEmpty()) {
			JVM.println();
			JVM.println("Reason: " + reason);
			JVM.println();
		} else
			JVM.println();
		JVM.println("Printing stacktrace.");
		if (index >= 0 && this.inst != null && !this.inst.isEmpty()) {
			final int defSize = this.inst.size() < 8 ? this.inst.size() : 8;
			for (int i = defSize - 1; i >= 0; i--) {
				final CupInstruction inst = this.inst.get(i);
				final String pref = i == defSize - 1 ? ">> " : "   ";
				JVM.println(pref + MathUtil.intToHexStr(inst.getInstructionID()) + " " + Arrays.toString(inst.getInstructionArguments()));
			}
		} else
			JVM.println("! No stack trace found.");
		JVM.println();
		JVM.println("Printing registers.");
		if (this.regs != null) {
			this.regs.asMap().entrySet().forEach((e) -> {
				Object val = e.getValue();
				if (val instanceof Integer)
					val = MathUtil.intToHexStr((int) val);
				else if (val instanceof Short)
					val = MathUtil.intToHexStr((short) val);
				JVM.println(e.getKey() + " = " + val);
			});
		} else
			JVM.println("! No registers found.");
		JVM.println();
		JVM.println("Due to the nature of this exception, the VEE cannot continue.");
		JVM.println();
		JVM.println("=======================================");
	}

	public static void main(String[] args) {
		final List<CupInstruction> insts = Lists.of(/* LRG 8 */ new LoadRegister(8), /* LRG 9 */ new LoadRegister(9),
				/* LNR "FTEST", 7 */ new LoadNearestRegister("FTEST", 7), /* LNR "CPUVEN" */ new LoadNearestRegister("CPUVEN"),
				/* MOV "VEEVEN", "CPUVEN" */ new MoveRegister("VEEVEN", "CPUVEN"), /* WTR 8, 12 */ new WriteToRegister(8, 12),
				/* WTR "FTEST", 9.26062109022 */ new WriteToRegister("FTEST", 9.26062109022f), /* CUPID 99, 9 */ new CUPIDQuery(99, 9),
				/* ADD 8, 2 */ new AddOperation(8, (short) 2), /* MUL 8, 4 */ new MultiplyOperation(8, (short) 4),
				/* SUB 8, 2 */ new SubtractOperation(8, (short) 2), /* DIV 8, 10 */ new DivideOperation(8, (short) 10),
				/* MOD 8, 2 */ new ModulusOperation(8, (short) 2), /* NUKE 8 */ new NukeRegister(8), /* COUT 9 */ new ConsoleOut("Hello world!"),
				/* LSR 11, "Test String" */ new LoadStringRegister(11, "Test String"), /* MUL "FTEST", 2 */ new MultiplyOperation("FTEST", 2f));
		final VEE vee = new VEE(CUPRegisterType.NORMAL);
		new CUPRunner(vee, insts).run();
		JVM.println("-> Execution finished, dumping registers.");
		vee.getRegisterManager().asMap().entrySet().forEach((e) -> {
			Object val = e.getValue();
			if (val instanceof Integer)
				val = MathUtil.intToHexStr((int) val);
			else if (val instanceof Short)
				val = MathUtil.intToHexStr((short) val);
			JVM.println(e.getKey() + " = " + val);
		});
	}
}
